package com.athena.framework.annotation;

import cn.hutool.core.io.resource.ClassPathResource;
import cn.hutool.core.util.StrUtil;
import com.athena.framework.beanfactory.BeanFactory;
import com.athena.framework.beanfactory.DefaultBeanFactory;
import com.athena.framework.config.DataConversion;
import com.athena.framework.config.DefaultDataConversion;
import com.athena.framework.exception.BeanException;
import com.athena.framework.exception.FileException;
import org.ho.yaml.Yaml;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

/**
 * YML注解解析
 */
public class YmlParsing implements AnnotationParsing {

    private final static Logger logger = LoggerFactory.getLogger(AnnotationParsing.class);

    private BeanFactory beanFactory = DefaultBeanFactory.creatBeanFactory();

    /**
     * 开始解析YML注解
     */
    @Override
    public void parsing() {
        Set<Class<?>> allYmlAnnotationClass = findAllScanPackgeByAnnotation(Yml.class);
        //分别为这些类型是指配置文件的值
        allYmlAnnotationClass.stream().forEach(clazz -> setContent(clazz));
    }

    /**
     * 为传入的类型设置对应的配置文件的值
     *
     * @param clazz
     */
    private void setContent(Class<?> clazz) {
        //查找该类型已经被托管的对象
        List beanClass = beanFactory.findBeanByClass(clazz);
        //如果框架 没有托管该类型的对象，那么就创建一个对象
        if (beanClass == null) {
            beanClass = new LinkedList<>();
            try {
                Object o = clazz.newInstance();
                beanFactory.addBean(o);
                beanClass.add(o);
            } catch (Exception e) {
                throw new BeanException("创建bean失败：" + clazz.getName() + "\r\n" + e.getMessage());
            }
        }
        Yml annotation = clazz.getAnnotation(Yml.class);
        String path = annotation.value();//获取配置文件的路径
        boolean isClassPath = annotation.isClassPath();//是否是读取classpath下的配置文件
        //解析yml配置文件
        Map<String, Object> yml = parsingYml(path, isClassPath);
        //循环的解析对象之中的属性
        beanClass.forEach(obj -> setInstanceValue(obj, yml));
    }

    /**
     * 为对象实例的属性设置配置文件中对应的值
     *
     * @param obj 配置对象的实例
     * @param yml 配置文件中的内容
     */
    private void setInstanceValue(Object obj, Map<String, Object> yml) {
        Class<?> objClass = obj.getClass();
        Field[] fields = objClass.getDeclaredFields();
        for (int i = 0; i < fields.length; i++) {
            Field f = fields[i];
            f.setAccessible(true);
            //获取该字段应该读取的配置名称
            String cfgName = parsingCfgName(f);
            if (cfgName != null) {
                //根据配置名称在配置文件内容找到值
                Object cfgValue = findValueByConfig(cfgName, yml);
                //为属性设置值
                try {
                    //转换数据并设置值
                    f.set(obj, conversionData(f, cfgValue));
                } catch (IllegalAccessException e) {
                    String msg = "为" + obj.getClass().getName() + "的属性" + f.getName() + "设置值失败！！！\r\n"
                            + e.getMessage();
                    if (logger.isErrorEnabled()) {
                        logger.error(msg);
                    }
                    throw new BeanException(msg);
                }
            }
        }
    }

    /**
     * 将配置中的字符串数据转换成对象属性对应类型的数据
     *
     * @param f        属性的字段
     * @param cfgValue 配置文件的值
     * @return 转换后可以设置值的对象
     */
    private Object conversionData(Field f, Object cfgValue) {
        YmlValue ymlValue = f.getAnnotation(YmlValue.class);
        if (ymlValue != null) {
            Class<? extends DataConversion> conversion = ymlValue.conversion();
            if (conversion == DefaultDataConversion.class) {
                return cfgValue;
            } else {
                Method method = null;
                try {
                    method = conversion.getMethod("conversion", Object.class, Class.class);
                    return method.invoke(conversion.newInstance(), cfgValue, f.getType());
                } catch (NoSuchMethodException e) {
                    logger.error(e.getMessage());
                    throw new BeanException("没有找到方法：conversion");
                } catch (IllegalAccessException e) {
                    logger.error(e.getMessage());
                    throw new BeanException("执行数据转换方法出现异常");
                } catch (InstantiationException e) {
                    logger.error(e.getMessage());
                    throw new BeanException("执行数据转换方法出现异常");
                } catch (InvocationTargetException e) {
                    logger.error(e.getMessage());
                    throw new BeanException("执行数据转换方法出现异常");
                }
            }
        }
        return null;
    }

    /**
     * 根据配置名称在配置文件内容找到值
     *
     * @param cfgName
     * @param yml
     * @return
     */
    private Object findValueByConfig(String cfgName, Map<String, Object> yml) {
        String[] names = StrUtil.split(cfgName, ".");
        for (int i = 0; i < names.length; i++) {
            Object o = yml.get(names[i]);
            if (o != null) {
                if (!(o instanceof HashMap)) {
                    return i == (names.length - 1) ? o : null;
                } else {
                    yml = (Map<String, Object>) o;
                }
            }
        }
        return null;
    }

    /**
     * 获取属性注解上配置的名称
     *
     * @param f
     * @return 配置名称
     */
    private String parsingCfgName(Field f) {
        YmlValue ymlValue = f.getAnnotation(YmlValue.class);
        if (ymlValue == null) {
            return null;
        }
        return ymlValue.value();
    }

    /**
     * 解析yml配置文件
     *
     * @param path        配置文件路径
     * @param isClassPath 配置文件是否在classpath下
     * @return
     */
    public Map<String, Object> parsingYml(String path, boolean isClassPath) {
        if (logger.isInfoEnabled()) {
            logger.info("开始解析文件：" + path);
        }
        InputStream configInputStream = readConfigFile(path, isClassPath);
        return (Map<String, Object>) Yaml.load(configInputStream);
    }

    /**
     * 读取配置文件
     * <p>
     * 如果配置文件不在classpath环境下，那么使用文件流去读取程序外部的配置文件
     *
     * @param path        配置文件路径
     * @param isClassPath 配置文件是否在classpath下
     * @return
     */
    private InputStream readConfigFile(String path, boolean isClassPath) {
        if (isClassPath) {
            ClassPathResource resource = new ClassPathResource(path);
            return resource.getStream();
        } else {
            File file = new File(path);
            if (file.exists()) {
                try {
                    return new FileInputStream(file);
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }
            } else {
                String msg = "没有找到文件：" + path;
                logger.error(msg);
                throw new FileException(msg);
            }
        }
        return null;
    }


}
